"""Functions for generating terrain and overworld maps"""

from __future__ import division

from pyre.core import dice, types

def apply_terrain(world, terrain):
    """
    terrain(World, dict): None
    Randomly selects a tile using the the terrain weights as a categorical for
    each location on the World
    """

    for x in range(world.cols):
        for y in range(world.rows):
            world.set_tile((x, y), *dice.sample_categorical(terrain))


def fence(world, boundry=types.ColoredChar('#')):
    """
    fence(World, ColoredChar, str): None
    Creates an impassible barrier around the edge of the World
    """

    for x in range(world.cols):
        world.set_tile((x, 0), boundry, False)
        world.set_tile((x, world.rows - 1), boundry, False)
    for y in range(world.rows):
        world.set_tile((0, y), boundry, False)
        world.set_tile((world.cols - 1, y), boundry, False)


def overworld(world, terrain):
    heightmap = _generate_heightmap(world.cols, world.rows)
    _apply_heightmap(world, heightmap, terrain)


def _apply_heightmap(world, heightmap, terrain):
    for x in range(world.cols):
        for y in range(world.rows):
            height = heightmap[x][y]
            entries = [entry for entry in terrain if height <= entry[0]]
            tile = min(entries, key=lambda entry: entry[0])[1]
            world.set_tile((x, y), translucient=True, *tile)


def _generate_heightmap(cols, rows, num_iters=250):
    hmap = [[0 for _ in range(rows)] for _ in range(cols)]
    rx, ry = cols // 8, rows // 8
    rx2, ry2 = rx ** 2, ry ** 2

    # raise num_iters ellipses, wrapping horizontally
    for _ in range(num_iters):
        cx, cy = dice.rand_coordinate(cols, rows)

        for dx in range(-rx, rx):
            for dy in range(-ry, ry):
                # wrap horizontally
                x, y = (cx + dx) % cols, cy + dy
                if y < 0 or y >= rows:
                    continue

                # find componentwise distance from center
                tx, ty = abs(cx - x), abs(cy - y)
                tx = min(tx, cols - tx)

                # if inside ellipse, raise height
                if tx ** 2 / rx2 + ty ** 2 / ry2 < 1:
                    hmap[x][y] += 1

    # smooth by averaging with neighbors
    for x in range(1, cols - 1):
        for y in range(1, rows - 1):
            hmap[x][y] = (hmap[x][y - 1] + hmap[x][y] + hmap[x][y + 1]) / 3
            hmap[x][y] = (hmap[x - 1][y] + hmap[x][y] + hmap[x + 1][y]) / 3

    # normalize between 0 and 1
    heights = [hmap[x][y] for x in range(cols) for y in range(rows)]
    low = min(heights)
    span = max(heights) - low
    for x in range(cols):
        for y in range(rows):
            hmap[x][y] = (hmap[x][y] - low) / span

    return hmap
